#include<iostream>
#include<string>
#include<fstream>
#include<algorithm>
#include<cctype>
#include<sstream>
#include<thread>
#include<queue>
#include<vector>
#include <mutex>
#include <condition_variable>
#include "pugixml.hpp"


using std::cin;
using std::cout;
using std::ifstream;
using std::ofstream;
using std::stringstream;
using std::string;
using std::endl;
using std::cerr;
using std::vector;


std::mutex mx;
std::mutex log_mx;
std::condition_variable cv;
std::queue<string> q;
bool finished = false;
ofstream of("cve.log");

string str_tolower(string s) {
    std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::tolower(c); } );
    return s;
}

const char* filenames[] = {"../../input/nvd/nvdcve-2.0-2002.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2003.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2004.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2005.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2006.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2007.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2008.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2009.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2010.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2011.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2012.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2013.xml"  
                    ,"../../input/nvd/nvdcve-2.0-2014.xml"
                    ,"../../input/nvd/nvdcve-2.0-2015.xml"
                    ,"../../input/nvd/nvdcve-2.0-2016.xml"
                    ,"../../input/nvd/nvdcve-2.0-2017.xml"
                    ,"../../input/nvd/nvdcve-2.0-modified.xml"};

void consumer() {
    string pkgname;
    {
        std::lock_guard<std::mutex> lk(mx);     
        if (q.empty()){
            return;
        }                                               
        pkgname = q.front();
        cout << pkgname << endl;
        q.pop();
    }
    while (true) {
        pugi::xml_document doc;
        
        for(auto &filename: filenames) {
    
            pugi::xml_parse_result result = doc.load_file(filename);
            
            auto root = doc.child("nvd");
            for (pugi::xml_node entry = root.child("entry"); entry; entry=entry.next_sibling("entry")) {
                bool found = false;
                //<vuln:vulnerable-software-list> <vuln:product>
                //<vuln:summary>P
                for(auto pd= entry.child("vuln:vulnerable-software-list").child("vuln:product"); pd; pd= pd.next_sibling("vuln:product")) {
                    string product = str_tolower(pd.first_child().value());
                    if (product.find(pkgname)!=string::npos) {
                        found=true;
                        break;
                    }
                }
                if (!found) {
                    string summary = str_tolower(entry.child("vuln:summary").first_child().value());
                    if (summary.find(pkgname) != string::npos) {
                        found = true;
                    }
                }
                if (found) {
                    std::lock_guard<std::mutex> lk(log_mx);                                
                    of << pkgname << "   "<< entry.child("vuln:cve-id").first_child().value() << endl;
                }
            }
        }
        
        {
            std::lock_guard<std::mutex> lk(mx);   
            if (q.empty()){
                break;
            }                                             
            pkgname = q.front();
            cout << pkgname << endl;
            q.pop();
        }
    }
}

void producer() {
    ifstream fin("../../input/allpackages.txt");
    if (!fin) {
        cerr<<"Open Failed" <<endl;
        return;
    }
    char buffer[4096];
    while (fin.getline(buffer, 4096)) {
        stringstream ss;
        ss.str(buffer);
        string pkgname;
        ss >> pkgname;
        q.push(pkgname);
    }
    fin.close(); 
}

void searchCve(){
    producer();
    const int thread_number = 20;
    std::thread threads[thread_number];
    for (int i=0;i<thread_number;++i) {
        threads[i] = std::thread(consumer);
    }
    for (int i=0;i<thread_number;++i){
        threads[i].join();
    }
}

int main() {
    searchCve();
    // json j = "{ \"happy\": true, \"pi\": 3.141 }"_json;    
    // cout<<j.dump()<<endl;
}
